package org.slusk.thynwor.corestructures;

import java.awt.Color;
import java.awt.Component;
import java.util.ArrayList;
import java.util.LinkedList;

import javax.swing.JLabel;

import org.slusk.thynwor.corestructures.Region.RegionType;
import org.slusk.thynwor.feedback.AbstractFeedbackProvider;
import org.slusk.thynwor.feedback.ThynworTrainingSample;
import org.slusk.thynwor.util.Coordinates;
import org.slusk.thynwor.util.Direction;
import org.slusk.thynwor.util.ThynworException;

import com.anji.hyperneat.nd.ActivatorND;
import com.anji.hyperneat.nd.NDFloatArray;
import com.anji.hyperneat.nd.NDIntArray;
import com.anji.hyperneat.onlinereinforcement.IOnlineLearningActivatorND;
import com.anji.hyperneat.onlinereinforcement.trainingbank.Feedback;
import com.anji.hyperneat.onlinereinforcement.trainingbank.Feedback.FeedbackType;
import com.anji.hyperneat.onlinereinforcement.trainingbank.TrainingSample;
import com.anji.hyperneat.onlinereinforcement.trainingbank.TrainingSequence;

/**
 * The Worker Bot class.  Can move and interact with Tasks. 
 * @author Shaun Lusk
 *
 */
public class Drone extends Bot implements IWorker {
	public enum SignalType {
		ASSISTFOOD
		, ASSISTTHREAT
	}

	public enum OnlineLearningType {
		NONE
		,SUPERVISED
		, REINFORCEMENT
		, BACKPROPREINFORCEMENT
	}
	
	// FIXME - Indicates input layer index for an input type
//	private static final int inputLayerPressure = 0;
//	private static final int inputLayerSignal = 1;
//	private static final int inputLayerBaseSignal = 2;
//	private static final int inputLayerFoodVision = 3;
	
	private final int sightRange;				// How far the bots can see, for certain nets  
	private final int actionWaitLimit;			// How long the bot should wait at a Resource before it should reevaluate the situation (disengage).
	private final int strength;
	private final int memoryLimit;				// How many state memories to keep at a time
	
	private boolean isTowingResource;			// Whether or not this bot is in a carrying state (possible to have an interactee, but not be carrying it)
	private Direction orientation;				// Direction this bot is facing.	
	private InteractableEntity interactee;		// The Resource this bot is interacting with.
	private Resource target;					// Intended for use with managerial directives.
	private int actionWaitCount;				// How long this bot has been waiting at a given Resource.
	private boolean isBeingTowed;				// Whether this bot is being towed with a resource; meaningless if singlAgentTowMode is false.
		
	private ActivatorND sasasNet;
	private NetActivatorSasas sasasNetActivator;
	private ActivatorND sasasSearchNet;
	private ActivatorND sasasTowNet;
	
	private AbstractFeedbackProvider feedbackProvider;
	
	private LinkedList<ThynworTrainingSample> memory;
	
	// Stats.  Be sure to include any stat variables in initializeStats() (for member var) or resetStats() (static var)
	private NDIntArray visitedCounts;
	
	
	/**
	 * Initialize constants and such.
	 */
	static {
		
	}
	

	public Drone(Environment environment, Coordinates coordinates) {
		super(environment, coordinates);
		this.sightRange = Integer.parseInt(config.getProperty("Drone.sightRange"));
		this.strength = Integer.parseInt(config.getProperty("Drone.strength"));
		this.actionWaitLimit = Integer.parseInt(config.getProperty("Drone.actionWaitLimit"));
		this.memoryLimit = Integer.parseInt(config.getProperty("Drone.memoryLimit"));
		this.sasasNet = environment.getSasasNet();
		
		init();
	}

	public Drone(Environment environment, Coordinates coordinates, ActivatorND net) {
		super(environment, coordinates);
		this.sightRange = Integer.parseInt(config.getProperty("Drone.sightRange"));
		this.strength = Integer.parseInt(config.getProperty("Drone.strength")); 
		this.actionWaitLimit = Integer.parseInt(config.getProperty("Drone.actionWaitLimit"));
		this.memoryLimit = Integer.parseInt(config.getProperty("Drone.memoryLimit"));
		this.sasasNet = net;
		
		init();
	}
	
	public Drone(Environment environment, Coordinates coordinates, ActivatorND searchNet, ActivatorND taskNet) {
		super(environment, coordinates);
		this.sightRange = Integer.parseInt(config.getProperty("Drone.sightRange"));
		this.strength = Integer.parseInt(config.getProperty("Drone.strength")); 
		this.actionWaitLimit = Integer.parseInt(config.getProperty("Drone.actionWaitLimit"));
		this.memoryLimit = Integer.parseInt(config.getProperty("Drone.memoryLimit"));
		this.sasasSearchNet = searchNet;
		this.sasasTowNet = taskNet;
		
		init();
	}
	
	protected void init() {		
		// Initialize network apparatus
				
		this.sasasNetActivator = new NetActivatorSasas();
		this.feedbackProvider = environment.getFeedbackProvider();
		this.memory = new LinkedList<ThynworTrainingSample>();
		
		this.actionWaitCount = 0;
		this.isTowingResource = false;
		this.orientation = null;
		this.interactee = null;
		this.isBeingTowed = false;
	}
	
	@Override
	protected void initializeStats() {
		visitedCounts = new NDIntArray(new int[] {3,3});
		visitedCounts.clear();
	}

	
	public void resetStats() {
		visitedCounts.clear();
	}
	
	@Override
	public void doTurnActions() {
		if (environment.isSingleAgentTowMode() && isBeingTowed) {
			// do nothing
		} else {
			if (null == interactee || isTowingResource) move();
			else {
				if (++actionWaitCount >= actionWaitLimit) {
					disengage();
				}
			}
		}  
		
		// If it moves too far away, disengage
		if (interactee != null && GridMap.calculateSpecialManhattanDistance(coords, interactee.getCoordinates()) > 2) {
			disengage();
			environment.incrementDroppedFood();
		}
		
		incrementVisitedCounts();
	}

	public boolean getIsInteracting() { return null != interactee; }
	public boolean readyToWork() {return (null == target && null == interactee); }
	public boolean isEngaged() {return interactee != null;}
	public InteractableEntity getInteractee() {return interactee;}
	@Override
	public EntityType getEntityType() {return EntityType.BOT;}
	public void setIsTowing(boolean b) {isTowingResource = b;}
	public boolean isTowingResource() {return isTowingResource;}
	public int getStrength() {return strength;}
	public void setIsBeingTowed(boolean b) {isBeingTowed = b;}
	public boolean getIsBeingTowed() {return isBeingTowed;}
	public Direction getOrientation() {return orientation;}
		

	private void incrementVisitedCounts() {
		// Calc coordinates of the current map section
		int x = (int) (coords.x / Math.round((map.getColumnCount() / 3.0)));
		int y = (int) (coords.y / Math.round(map.getRowCount() / 3.0));
		
		int count = 1 + visitedCounts.get(x, y);
		visitedCounts.set(count, x, y);
	}
	
	@Override
	public void move(Direction direction) {
		orientation = direction;
		Coordinates lastCoords = coords;
		Coordinates proposedCoordinates = GridMap.directionToCoordinates(direction, coords);
		try {
			map.moveEntityTo(this, proposedCoordinates);
			coords = proposedCoordinates;
		} catch (ThynworException e) {
			// Couldn't move
			lastCoords = null;
		}
		
		if (null != interactee && lastCoords != null) {	// check last coordinates to ensure the bot was able to make a move, to prevent moving resource
			// If it's a resource, and this bot is the lead tower, tow it
			if (EntityType.RESOURCE == interactee.getEntityType() 
					&& isTowingResource
					&& (
							((Resource)interactee).getLeadTowerId() == -1 
							|| ((Resource)interactee).getLeadTowerId() == getId()	// Only the lead tower should be able to move it (still requires two though).
						)
				) {
				((Resource)interactee).setLeadTowerId(this.getId());
				interactee.move(GridMap.coordinatesToDirection(interactee.getCoordinates(), lastCoords));				
			}
		}
	}


	@Override
	public void move() {
		MoveOrder mo = getMoveOrderFromNet();
		Coordinates lastCoords = coords;
				

		if (null == mo || mo.proposedCoordinates.equals(coords)) {
			// For now, bot just sits there
		} else if (map.isFree(mo.proposedCoordinates)) {
			try {
				orientation = mo.proposedDirection;
				map.moveEntityTo(this, mo.proposedCoordinates);
				coords = mo.proposedCoordinates;
			} catch (ThynworException e) {
				// Should never happen.
			}
		} else if (map.isInBounds(mo.proposedCoordinates) && null == interactee) {
			// Somethings in the way. Try to interact with it.
			interact(mo.proposedDirection);
			// For now, don't change last direction.
		}  else if (map.isInBounds(mo.proposedCoordinates) && null != interactee) {
			// Already had a resource but bumped another.  o.O
			
		} else {
			// Ran into wall
		}
		
		if (null != interactee && lastCoords != coords) {	// check last coordinates to ensure the bot was able to make a move, to prevent moving resource
			// If it's a resource, and this bot is the lead tower, tow it
			if (EntityType.RESOURCE == interactee.getEntityType() 
					&& isTowingResource
					&& (
							((Resource)interactee).getLeadTowerId() == -1 
							|| ((Resource)interactee).getLeadTowerId() == getId()	// Only the lead tower should be able to move it (still requires two though).
						)
				) {
				((Resource)interactee).setLeadTowerId(this.getId());
				interactee.move(GridMap.coordinatesToDirection(interactee.getCoordinates(), lastCoords));				
			}
		}
	}


	private MoveOrder getMoveOrderFromNet() {
		MoveOrder mo = null;
		mo = sasasNetActivator.activate();
		return mo;
	}
	
	public void interact(Coordinates proposedCoords) {
		orientation = GridMap.coordinatesToDirection(coords, proposedCoords);
		if (map.isInBounds(proposedCoords)) {
			Entity entity = map.get(proposedCoords).getContents();
			if (null != entity && (entity instanceof InteractableEntity)) {
				interactee = (InteractableEntity) entity;
				interactee.addInteractingAgent(this);
			}
		}
	}
	
	public void interact(Direction direction) {
		orientation = direction;
		Coordinates targetCoords = GridMap.directionToCoordinates(direction, coords);
		if (map.isInBounds(targetCoords)) {
			Entity entity = map.get(targetCoords).getContents();
			if (null != entity && (entity instanceof InteractableEntity)) {
				interactee = (InteractableEntity) entity;
				interactee.addInteractingAgent(this);
			}
		}
	}


	public void disengage() {
		actionWaitCount = 0;
		isTowingResource = false;
		isBeingTowed = false;
		interactee.removeInteractingAgent(this);
		clearInteractee();
	}

	public void forceDisengage() {
		actionWaitCount = 0;
		isTowingResource = false;
		isBeingTowed = false;
		clearInteractee();
	}

	public void clearInteractee() {interactee = null;}
	
	/**
	 * Return the signal coordinates, if this bot has an interactee and is not towing it (and therefore waiting for assistance)
	 * @return The coordinates of the signal
	 */
	public Coordinates getSignalCoordinates() {
		if (null == interactee || isTowingResource) return null;
		return interactee.getCoordinates();
	}
	
	
	@Override
	public Entity clone() {
		// TODO Auto-generated method stub
		return null;
	}

	public Coordinates getActualCoordsFromOutputCoords(Coordinates outputCoords) {
		Coordinates actualCoords = new Coordinates(0,0);
		switch (outputCoords.x) {
		case 0:
			actualCoords.x = coords.x - 1;
			break;
		case 1:
			actualCoords.x = coords.x;
			break;
		case 2:
			actualCoords.x = coords.x + 1;
			break;
		}

		switch (outputCoords.y) {
		case 0:
			actualCoords.y = coords.y - 1;
			break;
		case 1:
			actualCoords.y = coords.y;
			break;
		case 2:
			actualCoords.y = coords.y + 1;
			break;
		}

		return actualCoords;
	}
	
	public String toString() {return getInfo();}
	
	public String getInfo() {
		StringBuilder sb = new StringBuilder();
		sb.append(super.getInfo());
		sb.append("Orientation: " + orientation +"\n");
		sb.append("Interactee: " + (null == interactee ? "null" : interactee.getCoordinates()) + "\n");
		return sb.toString();
	}
	
	public void testSasasInputs() {
		sasasNetActivator.testBuildInputs();
	}
	
	/**
	 * FOR TESTING PURPOSES ONLY
	 * @param args
	 */
	public static void main(String[] args) {
		
	
	}
	

	@Override
	public void render(Component cell) {
		((JLabel)cell).setText(this.getDisplayCharacter()+"");
		cell.setBackground(new Color(50, 150, 255));
		((JLabel)cell).setIcon(null);
	}
	
	/**
	 * Class to make it simple to generically process different movement directives, i.e., by direction or by coordinates,
	 * since differing network designs may output one or the other. 
	 * @author slusk
	 *
	 */
	public class MoveOrder {
		public final Coordinates proposedCoordinates;
		public final Direction proposedDirection;
		
		protected MoveOrder(Coordinates proposedCoordinates) {
			this.proposedCoordinates = proposedCoordinates;
			this.proposedDirection = GridMap.coordinatesToDirection(coords, proposedCoordinates);
		}
		
		protected MoveOrder(Direction proposedDirection) {
			this.proposedDirection = proposedDirection;
			this.proposedCoordinates = GridMap.directionToCoordinates(proposedDirection, coords);
		}
	}
	
	
	private class NetActivatorSasas {
		private NDFloatArray lastOuts;
//		private NDFloatArray beforeLastOuts;
		private float[] signalInputs;
		
		public NetActivatorSasas() {
			lastOuts = new NDFloatArray(3,3,1);
//			beforeLastOuts = new NDFloatArray(3,3,1);
		}
		
		private MoveOrder activate() {
			NDFloatArray inputs = buildInputs();
			NDFloatArray outputs;
			if (environment.getUseTaskNet()) {
				outputs = isTowingResource ? sasasTowNet.next(inputs) : sasasSearchNet.next(inputs);
//				beforeLastOuts = lastOuts;
				lastOuts = outputs;
			} else {
				outputs = sasasNet.next( inputs );
			}

			Coordinates outputCoords = getMaxForDim(outputs);

			Coordinates actualCoords = getActualCoordsFromOutputCoords(outputCoords);
			MoveOrder mo = new MoveOrder(actualCoords);
			feedbackProvider.provideFitnessFeedBackToEnvironment(Drone.this, signalInputs, mo);
			
			if (environment.isUseOnlineReinforcement()) { 
				reinforce(mo, inputs, outputCoords, outputs);
			}

			
			return mo;
		}
		
		private Coordinates getMaxForDim(NDFloatArray outputs) {
			Coordinates bestCoords = null;
			float current, best = -1;
			
			for (int y = 0; y < 3; y++) {
				for (int x = 0; x < 3; x++) {
					current = outputs.get(x,y,0);
					if (bestCoords == null || current > best) {
						best = current;
						bestCoords = new Coordinates(x, y);
					}
				}
			}
			
			return bestCoords;
		}
		
		
		private void reinforce(MoveOrder mo, NDFloatArray inputs, Coordinates outputCoords, NDFloatArray outputs) {
			if (sasasNet instanceof IOnlineLearningActivatorND || sasasSearchNet instanceof IOnlineLearningActivatorND) {
				IOnlineLearningActivatorND oland;
				if (environment.getUseTaskNet()) {
					oland = (IOnlineLearningActivatorND)(isTowingResource ? sasasTowNet : sasasSearchNet);
				} else {
					oland = (IOnlineLearningActivatorND) sasasNet;
				}
				
				Feedback feedback = feedbackProvider.getFeedback(Drone.this, mo, inputs, outputCoords, outputs);
//				if (null != feedback && null != feedback.value) {
				if (feedback.type != FeedbackType.NEUTRAL) {
					if (!environment.getUseIntermittentTrainingOnly()) oland.updateNet(feedback.value);
					if (environment.getUseTrainingBank()) {
						addStateToMemory(inputs, outputs, feedback, outputCoords, oland, 1.0f);
						addSequenceToTrainingBank(inputs, feedback, oland);
					}
				} else {
					if (environment.getUseTrainingBank()) addStateToMemory(inputs, outputs, feedback, outputCoords, oland, 0.0f);
				}
			}
		}
		
		private void addStateToMemory(NDFloatArray inputs, NDFloatArray outputs, Feedback feedback, Coordinates outputCoords, IOnlineLearningActivatorND net, float weight) {
			if (memory.size() >= memoryLimit) memory.poll();
			
			ThynworTrainingSample sample = new ThynworTrainingSample(inputs, feedback, weight, net, outputCoords, outputs);
			memory.add(sample);
		}

		private void addSequenceToTrainingBank(NDFloatArray inputs,	Feedback feedback, IOnlineLearningActivatorND net) {
			TrainingSequence sequence = extractTrainingSequence();
			environment.addTrainingSequenceToBank(sequence);
			memory.clear();
		}

		private TrainingSequence extractTrainingSequence() {
			if (memory.peekLast().feedback.type == FeedbackType.PUNISHMENT) return new TrainingSequence(new TrainingSample[] {memory.removeLast()});
			
			TrainingSample[] sequence = new TrainingSample[0];
			ThynworTrainingSample last;
			LinkedList<TrainingSample> queue = new LinkedList<TrainingSample>();
			float weight = 1.0f;
			
			do {
				// there should always be at least one training sample in memory when this loop starts.
				last = memory.removeLast();
				if (last.feedback.type == FeedbackType.NEUTRAL) 
					feedbackProvider.convertTrainingSampleToReinforcement(last, weight);
//				last.weight = weight;
				weight = weight * 0.90f;
				queue.addFirst(last);
			} while (!memory.isEmpty() && memory.peekLast().feedback.type != FeedbackType.PUNISHMENT && weight > 0); 
			sequence = queue.toArray(sequence);
			
			return new TrainingSequence(sequence);
		}


		private NDFloatArray buildInputs() {
			NDFloatArray inputs = new NDFloatArray(3, 3, 3);
			
			int xoffset = coords.x - 1;
			int yoffset = coords.y - 1;
			
			// Pressure sensing
			for (int y = coords.y - 1; y <= coords.y + 1; y++) {
				for (int x = coords.x - 1; x <= coords.x + 1; x++) {
					int inputXcoord = x - xoffset;
					int inputYcoord = y - yoffset;
					
					float value;
					if (map.isInBounds(x, y)) {
						Entity e = map.get(x, y).getContents();
						Food f = e == null ? null : e instanceof Food ? (Food) e : null;
						value = e == null ? 1.0f : f == null ? 0.0f : f.isBeingTowed() ? 0.0f : 0.5f;
					} else {
						value = 0.0f;
					}
					
					
					inputs.set(value, inputXcoord, inputYcoord, 0);
				}
			}
			
			float[] foodInputs = populateFoodVision();
			float[] baseDirection = isTowingResource ? populateBaseDirection() : null;
			signalInputs = isTowingResource ? null : populateSignalInputs();
			float[] visitedMemory = isTowingResource ? null : populateVisitedMemory();
//			float[] teamMateSignals = populateTeammateSignals();
			
//			MatrixIterator beforeLast = beforeLastOuts.iterator();
			for (int i = 0; i < foodInputs.length; i++) {
				int z = 1;
				inputs.set(
					foodInputs[i]
					,1 + GridMap.neighborRelativeCoords[i].x
					,1 + GridMap.neighborRelativeCoords[i].y
					,z++
//					,s
				);
				if (isTowingResource) {
					inputs.set(
							baseDirection[i]
							,1 + GridMap.neighborRelativeCoords[i].x
							,1 + GridMap.neighborRelativeCoords[i].y
							,z++
	//						,s
					);
				} else {
					inputs.set(
							signalInputs[i]
							,1 + GridMap.neighborRelativeCoords[i].x
							,1 + GridMap.neighborRelativeCoords[i].y
							,z++
	//						,s
					);
				}
//				inputs.set(1.0f - lastOuts.get(1 + GridMap.neighborRelativeCoords[i].x, 1 + GridMap.neighborRelativeCoords[i].y, 0)
//						, 1 + GridMap.neighborRelativeCoords[i].x
//						,1 + GridMap.neighborRelativeCoords[i].y
//						,z++
//				);
//				inputs.set(beforeLast.next(), 1 + GridMap.neighborRelativeCoords[i].x
//						,1 + GridMap.neighborRelativeCoords[i].y
//						,z++
//				);
//				inputs.set(
//						teamMateSignals[i]
//						,1 + GridMap.neighborRelativeCoords[i].x
//						,1 + GridMap.neighborRelativeCoords[i].y
//						,z++
////						,s
//				);

//				float val = visitedCounts.get(1 + GridMap.neighborRelativeCoords[i].x, 1 + GridMap.neighborRelativeCoords[i].y, 0);
//				inputs.set(((float)environment.getTimeSteps() + 1 - val) / ((float)environment.getTimeSteps() + 1)
//					, 1 + GridMap.neighborRelativeCoords[i].x
//					, 1 + GridMap.neighborRelativeCoords[i].y
//					, z++
//				);
//				if (!isTowingResource) {
//					inputs.set(
//					visitedMemory[i]
//					,1 + GridMap.neighborRelativeCoords[i].x
//					,1 + GridMap.neighborRelativeCoords[i].y
//					,z++
//	//				,s
//					);
//				}

			}
			// fill in middle values
			// ** I don't have a clue what this was
//			inputs.set(1 - lastOuts.get(1,1,0), 1, 1, 3);
//			inputs.set(((float)environment.getTimeSteps() + 1 - visitedCounts.get(1,1,0)) / ((float)environment.getTimeSteps() + 1)
//					, 1
//					, 1
//					, 3);
			
			return inputs;
		}
		
		private float[] populateVisitedMemory() {
			float[] sigs = new float[8];
			for (int i = 0; i < sigs.length; i++) sigs[i] = 0.0f;
			
			int least = Integer.MAX_VALUE, nextLeast = Integer.MAX_VALUE;
			int[] leastCoords = null, nextLeastCoords = null;
			for (NDIntArray.MatrixIterator visited = visitedCounts.iterator(); visited.hasNext(); visited.next()) {
				int[] valCoords = visited.getCurrentCoordinates(); 
				int val = visited.get();
				if (val < least) {
					nextLeast = least;
					nextLeastCoords = leastCoords;
					leastCoords = valCoords;
					least = val;
				} else if (val < nextLeast){
					nextLeast = val;
					nextLeastCoords = valCoords;
				}
			}
			// translate sector to direction (based on center of sector)
			int x = leastCoords[0] * 7 + 3;
			int y = leastCoords[1] * 7 + 3;
			Coordinates c = new Coordinates(x, y);
			Direction d = map.coordinatesToDirection(coords, c);
			double distance = GridMap.calculateSpecialManhattanDistance(coords, c);
			// Inverse normalized distance - in some cases will favor next least, but that's fine, starvation should not occur
			sigs[d.ordinal()] += (float)(1.0d - map.normalizeSpecialManhattanDistance(distance));
			
			// now do next least
			x = nextLeastCoords[0] * 7 + 3;
			y = nextLeastCoords[1] * 7 + 3;
			c = new Coordinates(x, y);
			d = map.coordinatesToDirection(coords, c);
			distance = GridMap.calculateSpecialManhattanDistance(coords, c);
			sigs[d.ordinal()] += (float)(1.0d - map.normalizeSpecialManhattanDistance(distance));
			
			// It's possible both least and next least are in same direction.  Saturate to 1.0
			if (sigs[d.ordinal()] > 1) sigs[d.ordinal()] = 1.0f;
			
			return sigs;
		}

		private float[] populateTeammateSignals() {
			float[] sigs = new float[8];
			for (int i = 0; i < sigs.length; i++) sigs[i] = 0.0f;
//			LinkedList<Drone> drones = environment.getDroneList();
//			for (Drone drone : drones) {
//				if (drone.getId() != getId()) {
//					Direction d = GridMap.coordinatesToDirection(coords, drone.getCoordinates());
//					double distance = GridMap.calculateSpecialManhattanDistance(coords, drone.getCoordinates());
//					// Signal input is additive; alternative might be to only take closest (strongest)
//					// Inverse normalized distance - when using additive, closer or more signals = stronger input
//					sigs[d.ordinal()] += (float) (/*1.0d - */map.normalizeSpecialManhattanDistance(distance));
//					if (sigs[d.ordinal()] > 1) sigs[d.ordinal()] = 1;
//				}
//			}
			/* test code for exploration */
			for (Direction d : Direction.values()) {
				float val = 0;
				switch(d) {
				case N:
					if (coords.y > (map.getRowCount()/2)) val = 1.0f;
					break;
				case NE:
					if (coords.x < (map.getRowCount()/2) && coords.y > (map.getRowCount()/2)) val = 1.0f;
					break;
				case E:
					if (coords.x < (map.getRowCount()/2)) val = 1.0f;
					break;
				case SE:
					if (coords.x < (map.getRowCount()/2) && coords.y < (map.getRowCount()/2)) val = 1.0f;
					break;
				case S:
					if (coords.y < (map.getRowCount()/2)) val = 1.0f;
					break;
				case SW:
					if (coords.x > (map.getRowCount()/2) && coords.y < (map.getRowCount()/2)) val = 1.0f;
					break;
				case W:
					if (coords.x > (map.getRowCount()/2)) val = 1.0f;
					break;
				case NW:
					if (coords.x > (map.getRowCount()/2) && coords.y > (map.getRowCount()/2)) val = 1.0f;
					break;
				}
				sigs[d.ordinal()] = val/6.0f;
			}			
			return sigs;
		}

		private float[] populateBaseDirection() {
			float[] sigInputs = new float[8];
			for (int i = 0; i < sigInputs.length; i++) sigInputs[i] = 0.0f;
			LinkedList<Region> baseList = map.getRegionsOfType(RegionType.BASE);
			for (Region base : baseList) {
				Coordinates baseCenter = base.getCenter();
				Direction mainDirection = GridMap.coordinatesToDirection(coords, baseCenter);
				
				// For now just populate for "similar" directions
				// Could also do: only base direction, or all directions
				LinkedList<Direction> dirList = GridMap.getSimilarDirections(mainDirection);
				for (Direction dir : dirList) {
					Coordinates c = GridMap.directionToCoordinates(dir, coords);
					double distance = GridMap.calculateSpecialManhattanDistance(c, baseCenter);
					// Inverse normalized distance
					sigInputs[dir.ordinal()] += (float)(1.0d - map.normalizeSpecialManhattanDistance(distance));
				}
			}
			return sigInputs;
		}

		private float[] populateFoodVision() {
			float[] a = new float[8];
			for (int i = 0; i < 8; i++) a[i] = 0.0f;
			
			Coordinates topLeft = new Coordinates(
					Math.max(coords.x-sightRange, 0)
					, Math.max(coords.y-sightRange, 0)
			);
			Coordinates bottomRight = new Coordinates(
					Math.min(coords.x+sightRange, map.getColumnCount()-1)
					,Math.min(coords.y+sightRange, map.getRowCount()-1)
			);
			ArrayList<Entity> list = map.getListOfEntitiesInRange(topLeft, bottomRight, Food.class);
			
			for (Entity e : list) {
				Food f = (Food) e;
				if (!f.isBeingTowed()) {
					double distance = GridMap.calculateSpecialManhattanDistance(coords, f.getCoordinates());
					distance = getScaledDistance(distance);
					Direction direction = GridMap.coordinatesToDirection(coords, f.getCoordinates());
					a[direction.ordinal()] += distance;
					if (a[direction.ordinal()] > 1.0f) a[direction.ordinal()] = 1.0f;	
				}
			}
			
			return a;
		}
		
		private double getScaledDistance(double distance) {
			distance = (distance-1) / (sightRange-1 + 0.05); // normalize, add "epsilon" to sightRange
			distance = 1 - distance;	// Invert
//			distance = Math.pow(distance, 2);	// Square, to increase significance of differences.
			return distance;
		}
		
		public float[] populateSignalInputs() {
			float[] sigInputs = new float[8];
			for (int i = 0; i < sigInputs.length; i++) sigInputs[i] = 0.0f;

			if (isTowingResource) {
				for (int i = 0; i < sigInputs.length; i++) sigInputs[i] = 0.0f;
				LinkedList<Region> baseList = map.getRegionsOfType(RegionType.BASE);
				for (Region base : baseList) {
					Coordinates baseCenter = base.getCenter();
					Direction mainDirection = GridMap.coordinatesToDirection(coords, baseCenter);
					
					// For now just populate for "similar" directions
					// Could also do: only base direction, or all directions
					LinkedList<Direction> dirList = GridMap.getSimilarDirections(mainDirection);
					for (Direction dir : dirList) {
						Coordinates c = GridMap.directionToCoordinates(dir, coords);
						double distance = GridMap.calculateSpecialManhattanDistance(c, baseCenter);
						// Inverse normalized distance
						sigInputs[dir.ordinal()] += (float)(1.0d - map.normalizeSpecialManhattanDistance(distance));
					}
				}
			} else {
				LinkedList<Coordinates> signals = environment.buildSignalCoordinatesList();
				for (Coordinates signal : signals) {
					Direction d = GridMap.coordinatesToDirection(coords, signal);
					double distance = GridMap.calculateSpecialManhattanDistance(coords, signal);
					// Signal input is additive; alternative might be to only take closest (strongest)
					// Inverse normalized distance - when using additive, closer or more signals = stronger input
					sigInputs[d.ordinal()] += (float) (1.0d - map.normalizeSpecialManhattanDistance(distance));
				}
			}
			
			return sigInputs;
		}
		
		private void testBuildInputs() {
			int s = isTowingResource ? 1 : 0;
			NDFloatArray inputs = buildInputs();
			System.out.println(inputs);
		}
	}

}
